/*
*  Copyright (c) 2006-2020, Chukie
*
*  SPDX-License-Identifier: Apache-2.0
*
*  @file     : cmux.c
*
*  @brief    :
*  Website   : https://gitee.com/open-embedded
*
*
*  Change Logs:
*  Date           Author          Notes
*  2020-06-28     zhaocj       The first version
*/
/* Includes -----------------------------------------------------------------*/
#include "cmux.h"
#include <rthw.h>
#include <string.h>
#define DBG_TAG "cmux"

#ifdef CELLULAR_CMUX_USING_DEBUG
    #define DBG_LVL DBG_LOG
#else
    #define DBG_LVL DBG_INFO
#endif
#include <rtdbg.h>
/* Private typedef ----------------------------------------------------------*/
struct cmux_frame
{
    uint8_t flag;
    uint8_t addr;
    uint8_t ctrl;
    union len_info
    {
        uint8_t len[2];
        uint8_t val[2];
    } ALIGN(1) li;
    uint8_t value[1];
} ALIGN(1);

typedef struct cmux_frame cmux_frame_t;


/* Private define -----------------------------------------------------------*/
/* Private variables --------------------------------------------------------*/
const uint8_t  crctable[256] =      //reversed, 8-bit, poly=0x07
{
    0x00, 0x91, 0xE3, 0x72, 0x07, 0x96, 0xE4, 0x75,  0x0E, 0x9F, 0xED, 0x7C, 0x09, 0x98, 0xEA, 0x7B,
    0x1C, 0x8D, 0xFF, 0x6E, 0x1B, 0x8A, 0xF8, 0x69,  0x12, 0x83, 0xF1, 0x60, 0x15, 0x84, 0xF6, 0x67,
    0x38, 0xA9, 0xDB, 0x4A, 0x3F, 0xAE, 0xDC, 0x4D,  0x36, 0xA7, 0xD5, 0x44, 0x31, 0xA0, 0xD2, 0x43,
    0x24, 0xB5, 0xC7, 0x56, 0x23, 0xB2, 0xC0, 0x51,  0x2A, 0xBB, 0xC9, 0x58, 0x2D, 0xBC, 0xCE, 0x5F,

    0x70, 0xE1, 0x93, 0x02, 0x77, 0xE6, 0x94, 0x05,  0x7E, 0xEF, 0x9D, 0x0C, 0x79, 0xE8, 0x9A, 0x0B,
    0x6C, 0xFD, 0x8F, 0x1E, 0x6B, 0xFA, 0x88, 0x19,  0x62, 0xF3, 0x81, 0x10, 0x65, 0xF4, 0x86, 0x17,
    0x48, 0xD9, 0xAB, 0x3A, 0x4F, 0xDE, 0xAC, 0x3D,  0x46, 0xD7, 0xA5, 0x34, 0x41, 0xD0, 0xA2, 0x33,
    0x54, 0xC5, 0xB7, 0x26, 0x53, 0xC2, 0xB0, 0x21,  0x5A, 0xCB, 0xB9, 0x28, 0x5D, 0xCC, 0xBE, 0x2F,

    0xE0, 0x71, 0x03, 0x92, 0xE7, 0x76, 0x04, 0x95,  0xEE, 0x7F, 0x0D, 0x9C, 0xE9, 0x78, 0x0A, 0x9B,
    0xFC, 0x6D, 0x1F, 0x8E, 0xFB, 0x6A, 0x18, 0x89,  0xF2, 0x63, 0x11, 0x80, 0xF5, 0x64, 0x16, 0x87,
    0xD8, 0x49, 0x3B, 0xAA, 0xDF, 0x4E, 0x3C, 0xAD,  0xD6, 0x47, 0x35, 0xA4, 0xD1, 0x40, 0x32, 0xA3,
    0xC4, 0x55, 0x27, 0xB6, 0xC3, 0x52, 0x20, 0xB1,  0xCA, 0x5B, 0x29, 0xB8, 0xCD, 0x5C, 0x2E, 0xBF,

    0x90, 0x01, 0x73, 0xE2, 0x97, 0x06, 0x74, 0xE5,  0x9E, 0x0F, 0x7D, 0xEC, 0x99, 0x08, 0x7A, 0xEB,
    0x8C, 0x1D, 0x6F, 0xFE, 0x8B, 0x1A, 0x68, 0xF9,  0x82, 0x13, 0x61, 0xF0, 0x85, 0x14, 0x66, 0xF7,
    0xA8, 0x39, 0x4B, 0xDA, 0xAF, 0x3E, 0x4C, 0xDD,  0xA6, 0x37, 0x45, 0xD4, 0xA1, 0x30, 0x42, 0xD3,
    0xB4, 0x25, 0x57, 0xC6, 0xB3, 0x22, 0x50, 0xC1,  0xBA, 0x2B, 0x59, 0xC8, 0xBD, 0x2C, 0x5E, 0xCF
};

rt_slist_t cmux_list = RT_SLIST_OBJECT_INIT(cmux_list);
/* Extern variables ---------------------------------------------------------*/
/* Private function prototypes ----------------------------------------------*/
#ifndef RT_USING_ULOG
    #define log_hex(tag,width,buf,size)
#endif /*RT_USING_ULOG*/

#ifdef CELLULAR_CMUX_USING_DEBUG
    #define CMUX_DUMP(tips,data,len)                     log_hex(tips, 0, (uint8_t *)data, len)
#else
    #define CMUX_DUMP(tips,data,len)
#endif /*CELLULAR_CMUX_USING_DEBUG*/


#define CMUX_CHECK_FLAG(b)          ((b) == CMUX_BASIC_FLAG || (b) == CMUX_ADVANCE_FLAG)
#define CMUX_CHECK_ADDR(b)          ((b)&CMUX_ADDRESS_EA)

#define CMUX_CHECK_CTRL(b)          (((b)&(~CMUX_CONTROL_PF)) == CMUX_FRAME_TYPE_SABM || \
        ((b)&(~CMUX_CONTROL_PF)) == CMUX_FRAME_TYPE_UA || ((b)&(~CMUX_CONTROL_PF)) == CMUX_FRAME_TYPE_DM || \
        ((b)&(~CMUX_CONTROL_PF)) == CMUX_FRAME_TYPE_DISC || ((b)&(~CMUX_CONTROL_PF)) == CMUX_FRAME_TYPE_UIH || \
        ((b)&(~CMUX_CONTROL_PF)) == CMUX_FRAME_TYPE_UI)

#define CMUX_IS_UI_UIH(b)           (((b)&(~CMUX_CONTROL_PF)) == CMUX_FRAME_TYPE_UIH || \
        ((b)&(~CMUX_CONTROL_PF)) == CMUX_FRAME_TYPE_UI)

#define cmux_list_isempty()        rt_slist_isempty(&cmux_list)
#define cmux_list_foreach(node)    for ((node) = rt_slist_first(&cmux_list); (node); (node) = rt_slist_next(node))
#define cmux_list_append(c)        rt_slist_append(&cmux_list, &(c)->list)
#define cmux_list_remove(c)        rt_slist_remove(&cmux_list, &(c)->list)
#define cmux_list_entry(node)      rt_slist_entry(node, struct cmux, list)

cmux_t *cmux_get_by_real_device(rt_device_t dev)
{
    rt_slist_t *node;
    cmux_t *cmux;
    RT_ASSERT(dev);

    if (cmux_list_isempty())
    {
        CMUX_WARN("No available uart_frame instance!");
        return NULL;
    }

    cmux_list_foreach(node)
    {
        cmux = cmux_list_entry(node);
        if ((uintptr_t) cmux->device == (uintptr_t) dev)
        {
            return cmux;
        }
    }
    return NULL;
}

cmux_channel_t *cmux_get_channel_by_dlci(cmux_t *cmux, int dlci)
{
    cmux_channel_t *channel = NULL;
    RT_ASSERT(cmux);

    for (int i = 0; i < CELLULAR_CMUX_CHANNEL_NUM; i++)
    {
        channel = &cmux->channel_list[i];
        if (channel->dlci == dlci)
        {
            return channel;
        }
    }

    return NULL;
}

/**
 * @brief device receive indicate callback
 *
 * @param device
 * @param size
 * @return rt_err_t
 */
static rt_err_t device_rx_ind(rt_device_t device, rt_size_t size)
{
    cmux_t *cmux;
    if (!device)
    {
        CMUX_ERR("device is null!");
        return -RT_EINVAL;
    }

    cmux = cmux_get_by_real_device(device);

    if (cmux)
    {
        rt_event_send(&cmux->event, CMUX_EVENT_READ_DONE);
    }

    return RT_EOK;
}

/**
 * @brief device send indicate callback
 *
 * @param device
 * @param size
 * @return rt_err_t
 */
static rt_err_t device_tx_ind(rt_device_t device,  void *buffer)
{
    cmux_t *cmux;
    if (!device)
    {
        CMUX_ERR("device is null!");
        return -RT_EINVAL;
    }
    cmux = cmux_get_by_real_device(device);

    if (cmux)
    {
        rt_sem_release(&cmux->tx_sem);
    }

    return RT_EOK;
}

static int device_write(cmux_t *cmux, const uint8_t *data, size_t len, int timeout_ms)
{
    int wrote = 0;
    ASSERT(cmux);
    ASSERT(data);
    int timeout = timeout_ms >= 0 ? (int)rt_tick_from_millisecond(timeout_ms) : RT_WAITING_FOREVER;
    if (!cmux->device || cmux->device->ref_count < 1)
    {
        CMUX_ERR("Device is unavailable!");
        return -RT_EIO;
    }

    if ((cmux->device->open_flag & RT_DEVICE_FLAG_DMA_TX))
    {
        rt_sem_control(&cmux->tx_sem, RT_IPC_CMD_RESET, RT_NULL);
        wrote = rt_device_write(cmux->device, 0, data, len);
        if (rt_sem_take(&cmux->tx_sem, timeout) != RT_EOK)
        {
            CMUX_ERR("Write data timeout!");
            wrote = -RT_EIO;
        }
    }
    else
    {
        wrote = rt_device_write(cmux->device, 0, data, len);
    }
    return wrote;
}

static int device_open(cmux_t *cmux)
{
    rt_uint16_t open_flag = RT_DEVICE_FLAG_RDWR;
    rt_base_t level;

    while (cmux->device->ref_count > 0)
    {
        CMUX_WARN("The device %s may be in use,now close it!", cmux->device->parent.name);
        rt_device_close(cmux->device);
    }
    rt_sem_control(&cmux->tx_sem, RT_IPC_CMD_RESET, NULL);
    if ((cmux->device->flag & RT_DEVICE_FLAG_DMA_TX))
    {
        open_flag |= RT_DEVICE_FLAG_DMA_TX;
        level = rt_hw_interrupt_disable();
        rt_device_set_tx_complete(cmux->device, device_tx_ind);
        rt_hw_interrupt_enable(level);
        CMUX_DBG("Enable DMA TX!");
    }
    else
    {
        if (cmux->device->flag & RT_DEVICE_FLAG_INT_TX)
        {
            open_flag |= RT_DEVICE_FLAG_INT_TX;
        }
        level = rt_hw_interrupt_disable();
        rt_device_set_tx_complete(cmux->device, NULL);
        rt_hw_interrupt_enable(level);
    }

    if (cmux->device->flag & RT_DEVICE_FLAG_DMA_RX)
    {
        open_flag |= RT_DEVICE_FLAG_DMA_RX;
    }
    else
    {
        open_flag |= RT_DEVICE_FLAG_INT_RX;
    }

    level = rt_hw_interrupt_disable();
    ((struct rt_serial_device *)(cmux->device))->config.bufsz = RT_ALIGN(CMUX_FRAME_BUFFER_SIZE, 4);
    rt_device_set_rx_indicate(cmux->device, device_rx_ind);
    rt_hw_interrupt_enable(level);

    if (RT_EOK != rt_device_open(cmux->device, open_flag))
    {
        CMUX_ERR("Can't open device %s", cmux->device->parent.name);
        level = rt_hw_interrupt_disable();
        rt_device_set_rx_indicate(cmux->device, NULL);
        rt_device_set_tx_complete(cmux->device, NULL);
        rt_hw_interrupt_enable(level);
        return -RT_EIO;
    }
    CMUX_INFO("Open %s with flag: 0x%X ok!", cmux->device->parent.name, open_flag);

    return RT_EOK;
}

static int device_close(cmux_t *cmux)
{
    rt_base_t level;
    if (cmux->device->ref_count > 0)
    {
        rt_device_close(cmux->device);
        level = rt_hw_interrupt_disable();
        rt_device_set_rx_indicate(cmux->device, NULL);
        rt_device_set_tx_complete(cmux->device, NULL);
        rt_hw_interrupt_enable(level);
    }

    return RT_EOK;
}

int cmux_raw_send(cmux_t *cmux, uint8_t dlci, uint8_t frame_type, uint8_t *data, size_t len)
{
    uint8_t header_size = 4;
    uint8_t header[5] = {0};
    uint8_t ender[2] = {0};
    RT_ASSERT(cmux);
    rt_mutex_take(&cmux->write_lock, RT_WAITING_FOREVER);
    len = data == NULL ? 0 : len;
    header[0] = cmux->advance_mode ? CMUX_ADVANCE_FLAG : CMUX_BASIC_FLAG;
    header[1] = CMUX_ADDRESS_EA | CMUX_ADDRESS_CR | ((dlci & CMUX_MAX_DHCL_NUM) << 2);
    header[2] = frame_type;
    ender[0] = 0xFF;
    ender[1] = cmux->advance_mode ? CMUX_ADVANCE_FLAG : CMUX_BASIC_FLAG;
    ender[0] = crctable[ender[0] ^ header[1]];
    ender[0] = crctable[ender[0] ^ header[2]];
    if (!cmux->advance_mode)
    {
        if (len > CMUX_DATA_LEN_LOW_MAX)
        {
            header_size = 5;
            header[3] = ((CMUX_DATA_LEN_LOW_MAX & len) << 1);
            header[4] = (CMUX_DATA_LEN_HIGH_MAX & len) >> 7;
            ender[0] = crctable[ender[0] ^ header[3]];
            ender[0] = crctable[ender[0] ^ header[4]];
        }
        else
        {
            header[3] = (len << 1) | 1;
            header_size = 4;
            ender[0] = crctable[ender[0] ^ header[3]];
        }
    }
    else
    {
        header_size = 3;
    }

    ender[0] = 0xFF - ender[0];
    device_write(cmux, header, header_size, 1000);
    if (data && len)
    {
        device_write(cmux, data, len, 1000);
    }
    device_write(cmux, ender, 2, 1000);
    CMUX_DUMP("CMUX TX-header", header, header_size);
    if (len > 0)
    {
        CMUX_DUMP("CMUX TX-data", data, len);
    }
    CMUX_DUMP("CMUX TX-ender", ender, 2);
    rt_mutex_release(&cmux->write_lock);
    return len;
}

static int cmux_parse(cmux_t *cmux)
{
    int i = 0;
    int info_len = 0;
    int start_pos = 0, info_pos = 0;
    uint8_t dlci = 0, ctrl = 0, fcs = 0xFF, recv_fcs = 0;
    cmux_channel_t *channel = NULL;
    cmux_state_t state = CMUX_STATE_PARSE_START;
    CMUX_DBG("parse %dB data: ", cmux->recv_len);
    CMUX_DUMP("CMUX PARSE", cmux->recv_buf, cmux->recv_len);
    while (i < cmux->recv_len)
    {
        uint8_t data = cmux->recv_buf[i];

        switch (state)
        {
        case CMUX_STATE_PARSE_START:
        {
            if ((cmux->advance_mode && CMUX_ADVANCE_FLAG == data) || \
                    (!cmux->advance_mode && CMUX_BASIC_FLAG == data))
            {
                state = CMUX_STATE_PARSE_ADDR;
                start_pos = i;
                fcs = 0xFF;
            }
            break;
        }
        case CMUX_STATE_PARSE_ADDR:
        {
            fcs = crctable[fcs ^ data];
            if (CMUX_CHECK_ADDR(data))
            {
                state = CMUX_STATE_PARSE_CTRL;
                dlci = data >> 2;
                if (dlci > CMUX_MAX_DHCL_NUM)
                {
                    state = CMUX_STATE_PARSE_START;
                }
            }
            else
            {
                state = CMUX_STATE_PARSE_START;
            }
            break;
        }
        case CMUX_STATE_PARSE_CTRL:
        {
            fcs = crctable[fcs ^ data];
            ctrl = data & (~CMUX_CONTROL_PF);
            if (CMUX_CHECK_CTRL(data))
            {
                if (cmux->advance_mode)
                {
                    if (CMUX_FRAME_TYPE_UIH == ctrl || CMUX_FRAME_TYPE_UI == ctrl)
                    {
                        state = CMUX_STATE_PARSE_INFO;
                    }
                    else
                    {
                        state = CMUX_STATE_PARSE_FCS;
                    }
                }
                else
                {
                    state = CMUX_STATE_PARSE_LEN;
                }
            }
            else
            {
                state = CMUX_STATE_PARSE_START;
            }
            break;
        }
        case CMUX_STATE_PARSE_LEN:
        {
            fcs = crctable[fcs ^ data];
            info_len = (data & 254) >> 1;
            if (!(data & CMUX_DATA_LEN_EA))
            {
                if ((i + 1) < cmux->recv_len)
                {
                    i++;
                    data = cmux->recv_buf[i];
                    fcs = crctable[fcs ^ data];
                    info_len += (data << 7);
                    if (CMUX_FRAME_TYPE_UIH == ctrl || CMUX_FRAME_TYPE_UI == ctrl)
                    {
                        state = CMUX_STATE_PARSE_INFO;
                    }
                    else
                    {
                        state = CMUX_STATE_PARSE_FCS;
                    }
                }
                else
                {
                    //CMUX_WARN("incomplete pkt!");
                    goto check_and_move;
                }
            }
            else
            {
                if (CMUX_FRAME_TYPE_UIH == ctrl || CMUX_FRAME_TYPE_UI == ctrl)
                {
                    state = CMUX_STATE_PARSE_INFO;
                }
                else
                {
                    state = CMUX_STATE_PARSE_FCS;
                }
            }
            break;
        }
        case CMUX_STATE_PARSE_INFO:
        {
            info_pos = i;
            state = CMUX_STATE_PARSE_FCS;
            if (!cmux->advance_mode)
            {
                if ((i + info_len) >= (cmux->recv_len))
                {
                    CMUX_DBG("incomplete pkt!");
                    CMUX_DBG("cur pos->%d; info_len->%d; total_len->%d", i, info_len, cmux->recv_len);
                    goto check_and_move;
                }
                else
                {
                    i += info_len;
                    CMUX_DBG("info pose=%d; info[0]=%X; recv_fcs[%d]=%X", info_pos, cmux->recv_buf[info_pos], i, cmux->recv_buf[i]);
                }
            }
            break;
        }
        case CMUX_STATE_PARSE_FCS:
        {
            if ((cmux->advance_mode && CMUX_ADVANCE_FLAG == data) || \
                    (!cmux->advance_mode && CMUX_BASIC_FLAG == data))
            {
                recv_fcs = cmux->recv_buf[i - 1];

                if (0xCF != crctable[fcs ^ recv_fcs])
                {
                    CMUX_DBG("FCS error!");
                    CMUX_DBG("start_pos: %d; cur pos: %d", start_pos, i);
                }
                else
                {
                    state = CMUX_STATE_PARSE_END;
                    if (cmux->advance_mode)
                    {
                        info_len = i - info_pos - 1;
                    }
                    else
                    {
                        if ((CMUX_FRAME_TYPE_UIH == ctrl || CMUX_FRAME_TYPE_UI == ctrl) && (info_len != (i - info_pos - 1)))
                        {
                            CMUX_WARN("UIH len(%d) != real len(%d)", info_len, (i - info_pos - 1));
                            //LOG_HEX("BUF",0, cmux->recv_buf, cmux->recv_len);
                            info_len = (i - info_pos - 1);
                        }
                    }
                }
            }
            break;
        }
        case CMUX_STATE_PARSE_END:
            break;
        default:
            break;
        }
        i++;
        if (state == CMUX_STATE_PARSE_END)
        {
            break;
        }
    }
check_and_move:
    CMUX_DBG("start_pos: %d; end_pos: %d;info len: %d", start_pos, i, info_len);
    CMUX_DBG("state: %d; ctrl: %X; dlci: %d", state, ctrl, dlci);
    if (CMUX_STATE_PARSE_END == state)
    {
        channel = cmux_get_channel_by_dlci(cmux, dlci);
        if (channel)
        {
            switch (ctrl)
            {
            case CMUX_FRAME_TYPE_SABM:
                CMUX_DBG("Recv SABM frame for channel->DLCI[%d], drop it!", dlci);
                break;
            case CMUX_FRAME_TYPE_UA:
                CMUX_DBG("Recv UA frame for DLCI[%d]!", dlci);
                CMUX_DBG("Channel->DLCI[%d] connected!", dlci);
                rt_event_send(&cmux->event, CMUX_EVENT_CHANNEL_CREATE_ACK(dlci));
                break;
            case CMUX_FRAME_TYPE_DM:
                CMUX_DBG("Recv DM frame for DLCI[%d]!", dlci);
                break;
            case CMUX_FRAME_TYPE_DISC:
                CMUX_DBG("Recv DISC frame for DLCI[%d]!", dlci);
                break;
            case CMUX_FRAME_TYPE_UIH:
            case CMUX_FRAME_TYPE_UI:
                CMUX_DBG("Recv UI/UIH frame for DLCI[%d]!", dlci);
                if (info_len > 0)
                {
                    CMUX_DBG("Got %dB frame data: ", info_len);
                    CMUX_DUMP("CMUX RX", &cmux->recv_buf[info_pos], info_len);

                    if (channel->device.ref_count > 0)
                    {
                        rt_base_t level = rt_hw_interrupt_disable();
                        int len = rt_ringbuffer_space_len(&channel->rb);
                        if (len < info_len)
                        {
                            CMUX_WARN("channel[%d] buf is full, drop %dB data!", dlci, info_len - len);
                        }
                        else
                        {
                            len = info_len;
                        }
                        len = rt_ringbuffer_put(&channel->rb, &cmux->recv_buf[info_pos], len);
                        rt_hw_interrupt_enable(level);

                        if (len > 0)
                        {
                            if (channel->device.rx_indicate)
                            {
                                channel->device.rx_indicate(&channel->device, len);
                            }
                        }
                    }
                    else
                    {
                        CMUX_WARN("channel for DLCI[%d] is not open!", dlci);
                    }
                }
                break;
            default:
                break;
            }
        }
        else
        {
            CMUX_WARN("Can't find channel with DLCI %d", dlci);
        }
        cmux->recv_len -= i;
        if (cmux->recv_len > 0)
        {
            rt_memmove(cmux->recv_buf, &cmux->recv_buf[i], cmux->recv_len);
            cmux_parse(cmux);
        }
    }
    else if (state >= CMUX_STATE_PARSE_CTRL)
    {
        CMUX_DBG("Incomplet frame!");
        if (start_pos > 0)
        {
            cmux->recv_len -= (i - start_pos);
            if (cmux->recv_len > 0)
            {
                rt_memmove(cmux->recv_buf, &cmux->recv_buf[start_pos], cmux->recv_len);
            }
        }
    }
    else
    {
        cmux->recv_len -= i;
        if (cmux->recv_len > 0)
        {
            rt_memmove(cmux->recv_buf, &cmux->recv_buf[i], cmux->recv_len);
            cmux_parse(cmux);
        }
    }

    return cmux->recv_len;
}

static void cmux_thread_entry(void *args)
{
    rt_uint32_t event;
    RT_ASSERT(args);
    cmux_t *cmux = (cmux_t *)args;
    CMUX_INFO("Start...");
    rt_enter_critical();
    cmux->recv_len = 0;
    cmux->running = RT_TRUE;
    rt_exit_critical();
    rt_event_control(&cmux->event, RT_IPC_CMD_RESET, RT_NULL);
    if (RT_EOK != device_open(cmux))
    {
        cmux->running = RT_FALSE;
    }
    while (cmux->running)
    {
        rt_event_recv(&cmux->event, CMUX_EVENT_READ_DONE | CMUX_EVENT_EXIT_REQ, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &event);
        if (event & CMUX_EVENT_READ_DONE)
        {
            if (cmux->recv_len >= CMUX_FRAME_BUFFER_SIZE)
            {
                cmux->recv_len = 0;
            }
            int ret = rt_device_read(cmux->device, 0, cmux->recv_buf + cmux->recv_len, CMUX_FRAME_BUFFER_SIZE - cmux->recv_len);
            if (ret > 0)
            {
                cmux->recv_len += ret;
            }

            if (cmux->recv_len > 0)
            {
                cmux_parse(cmux);
            }
        }
        if (event & CMUX_EVENT_EXIT_REQ)
        {
            CMUX_INFO("Recv exit event,exit...");
            cmux->running = RT_FALSE;
            break;
        }
    }
    cmux_raw_send(cmux, 0, CMUX_FRAME_TYPE_DISC, NULL, 0);
    device_close(cmux);
    rt_enter_critical();
    cmux->running = RT_FALSE;
    rt_exit_critical();
    rt_event_send(&cmux->event, CMUX_EVENT_EXIT_ACK);
}


/* Public function prototypes -----------------------------------------------*/

int cmux_init(cmux_t *cmux, const char *uart_name, int advance_mode)
{
    rt_uint8_t count = rt_slist_len(&cmux_list);
    char name[RT_NAME_MAX] = {0};
    rt_slist_t *node;

    RT_ASSERT(cmux);
    memset(cmux, 0, sizeof(cmux_t));
    cmux->device = rt_device_find(uart_name);
    if (NULL == cmux->device)
    {
        CMUX_ERR("Can't find uart device: %s", uart_name);
        return  -RT_ENOSYS;
    }

    cmux_list_foreach(node)
    {
        cmux_t *tmp = cmux_list_entry(node);
        if (((uintptr_t)tmp == (uintptr_t)cmux) || ((uintptr_t)(cmux->device) == (uintptr_t)(tmp->device)))
        {
            CMUX_INFO("cmux rely on %s has been init!", uart_name);
            return RT_EOK;
        }
    }
    rt_snprintf(cmux->name, sizeof(cmux->name), "cmux%d(%s)", count, uart_name);

    rt_snprintf(name, sizeof(name), "cmux%d_event", count);
    rt_event_init(&cmux->event, name, RT_IPC_FLAG_PRIO);

    rt_snprintf(name, sizeof(name), "cmux%d_wl", count);
    rt_mutex_init(&cmux->write_lock, name, RT_IPC_FLAG_PRIO);

    rt_snprintf(name, sizeof(name), "cmux%d_tx_sem", count);
    rt_sem_init(&cmux->tx_sem, name, 0, RT_IPC_FLAG_PRIO);
    cmux->advance_mode = advance_mode;

    for (int i = 0; i < CELLULAR_CMUX_CHANNEL_NUM; i++)
    {
        cmux->channel_list[i].parent = cmux;
        cmux->channel_list[i].dlci = 0;
    }

    cmux_list_append(cmux);
    return RT_EOK;
}

int cmux_start(cmux_t *cmux)
{
    RT_ASSERT(cmux);
    if (cmux->running && RT_THREAD_CLOSE != (cmux->thread.stat & RT_THREAD_STAT_MASK) && \
            RT_THREAD_INIT != (cmux->thread.stat & RT_THREAD_STAT_MASK))
    {
        CMUX_WARN("cmux rely on %s is running!", cmux->device->parent.name);
        return RT_EOK;
    }

    if (RT_EOK != rt_thread_init(&cmux->thread, cmux->name, cmux_thread_entry, \
                                 cmux, cmux->stack, CMUX_THREAD_STACK_SIZE, CMUX_THREAD_PRIO, 64))
    {
        CMUX_ERR("Init thread %s failed!", cmux->name);
        return -RT_ERROR;
    }

    if (RT_EOK == rt_thread_startup(&cmux->thread))
    {
        return RT_EOK;
    }
    return -RT_ERROR;
}

int cmux_stop(cmux_t *cmux)
{

    rt_uint32_t event;
    RT_ASSERT(cmux);
    if (cmux->running)
    {
        CMUX_INFO("Stop cmux(relay on %s)...", cmux->device->parent.name);
        rt_event_send(&cmux->event, CMUX_EVENT_EXIT_REQ);
        if (RT_EOK != rt_event_recv(&cmux->event, CMUX_EVENT_EXIT_ACK, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, \
                                    rt_tick_from_millisecond(5000), &event))
        {
            if (RT_THREAD_CLOSE != (cmux->thread.stat & RT_THREAD_STAT_MASK) && \
                    RT_THREAD_INIT != (cmux->thread.stat & RT_THREAD_STAT_MASK))
            {
                CMUX_WARN("Stop cmux failed, detach it...");
                rt_thread_detach(&cmux->thread);
                cmux_raw_send(cmux, 0, CMUX_FRAME_TYPE_DISC, NULL, 0);
                device_close(cmux);
            }
        }

        rt_sem_control(&cmux->tx_sem, RT_IPC_CMD_RESET, NULL);
        cmux->running = RT_FALSE;
    }

    return RT_EOK;
}

/* Define to prevent recursive inclusion ------------------------------------*/

/* End of file****************************************************************/

